package s3util

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"gitee.com/ymofen/gobase"
	"gitee.com/ymofen/gobase/factory"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"io"
	"net"
	"net/http"
	"os"
	"sync"
	"time"
)

type S3ClientWrapper struct {
	lk       sync.RWMutex
	busyflag int32

	connString string
	s3c        *s3.Client

	PoolSize     int
	LastErr      string
	hincrbyIfSha string

	setIfSha  string
	hsetIfSha string
}

var defHttpClient = http.Client{
	Transport: &http.Transport{
		TLSClientConfig:       &tls.Config{InsecureSkipVerify: true}, //跳过证书的有效认证
		MaxConnsPerHost:       200,
		MaxIdleConns:          100,
		MaxIdleConnsPerHost:   2,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: -1,
		}).DialContext,
	},
	Timeout: time.Minute * 121, //设置超时时间，121 分钟
}

func NewS3ClientWrapper() *S3ClientWrapper {
	rval := &S3ClientWrapper{}
	return rval
}

// 是否有效
func (this *S3ClientWrapper) Valid() bool {
	this.lk.RLock()
	defer this.lk.RUnlock()
	return len(this.connString) > 0
}

func (this *S3ClientWrapper) GetConnString() string {
	this.lk.RLock()
	defer this.lk.RUnlock()
	return this.connString
}

func (this *S3ClientWrapper) UpdateConnString(s string) {
	this.lk.Lock()
	defer this.lk.Unlock()
	if this.connString != s {
		this.connString = s
		this.resetConnection()
	}
}

func (this *S3ClientWrapper) Close() error {
	this.resetConnection()
	return nil
}

func (this *S3ClientWrapper) withDeadlineCtx(dura time.Duration) context.Context {
	ctx, _ := context.WithTimeout(context.Background(), dura)
	// ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Millisecond*100))
	return ctx
}

func (this *S3ClientWrapper) withWriteDeadlineCtx() context.Context {
	return this.withDeadlineCtx(time.Second * 10)
}

func (w *S3ClientWrapper) resetConnection() {
	if w.s3c != nil {
		w.s3c = nil
	}
}

// ===== 初始化 S3 客户端（推荐方式：使用 EndpointResolverV2）=====
func (w *S3ClientWrapper) initS3Client(confMap gobase.StrMap) (*s3.Client, error) {
	region := confMap["region"]
	if region == "" {
		region = "cn-wh-1"
	}
	endpoint := confMap["endpoint"]
	partitionID := confMap["partitionID"]
	if len(partitionID) == 0 {
		partitionID = "d10"
	}

	accessKeyID := confMap["accessKeyID"]
	secretAccessKey := confMap["secretAccessKey"]

	// 1. 先加载默认配置（比如 region），但不设置全局 endpoint
	cfg, err := config.LoadDefaultConfig(w.withDeadlineCtx(time.Second*5),
		config.WithRegion(region),
	)
	if err != nil {
		return nil, fmt.Errorf("LoadDefaultConfig err: %v", err)
	}

	// 2. 创建 S3 客户端，并通过 EndpointResolverV2 设置 MinIO 的 endpoint 和认证信息
	s3c := s3.NewFromConfig(cfg, func(o *s3.Options) {
		// 适配新版本的 EndpointResolver 接口
		o.EndpointResolver = s3.EndpointResolverFunc(func(region string, options s3.EndpointResolverOptions) (aws.Endpoint, error) {
			return aws.Endpoint{
				URL:           endpoint,
				PartitionID:   partitionID,
				SigningRegion: region,
			}, nil
		})

		// 设置 MinIO 的 Access Key 和 Secret Key
		o.Credentials = aws.CredentialsProviderFunc(func(ctx context.Context) (aws.Credentials, error) {
			return aws.Credentials{
				AccessKeyID:     accessKeyID,
				SecretAccessKey: secretAccessKey,
				Source:          "Static credentials from code",
			}, nil
		})

		// MinIO 通常使用 Path Style（尤其是通过 IP 访问时）
		o.UsePathStyle = true
	})

	return s3c, nil
}

// const (
//	endpoint          = "http://58.49.94.131:18380"          // MinIO 服务地址
//	accessKeyID       = "yangmf"                             // MinIO Access Key
//	secretAccessKey   = "159357aa"                           // MinIO Secret Key
//	region            = "us-east-1"                          // MinIO 一般随便填，比如 us-east-1
//	bucketName        = "ephpre"                             // Bucket 名称
//	localUploadPath   = "D:\\raw\\logo.png"                  // 本地待上传文件路径
//	localDownloadPath = "D:\\downloads\\logo-downloaded.png" // 下载后的本地路径
//	objectKey         = "logo.png"                           // MinIO 中存储的对象键（即文件名）
//)

// reconnect 尝试建立新的 gRPC 连接并创建客户端存根
func (this *S3ClientWrapper) reconnect() (*s3.Client, error) {
	this.resetConnection()

	confMap := gobase.NewStrMap()
	confMap.URLFormDecode(this.connString)

	rval, err := this.initS3Client(confMap)
	if err != nil {
		return nil, err
	}
	this.s3c = rval
	return this.s3c, nil
}

// getOrCreateClient 获取当前可用的客户端存根，如果连接失效则自动重连
func (w *S3ClientWrapper) getOrCreateClient() (clt *s3.Client, err error) {
	w.lk.RLock()
	if w.s3c != nil {
		clt = w.s3c
		w.lk.RUnlock()
		return
	}
	w.lk.RUnlock()

	w.lk.Lock()
	defer w.lk.Unlock()
	return w.reconnect()
}

func (w *S3ClientWrapper) innerCheckGetClient() (clt *s3.Client, err error) {
	if w.s3c != nil {
		clt = w.s3c
		return clt, nil
	}
	return w.reconnect()
}

func (this *S3ClientWrapper) checkInvalidConnectionError(err error) {
	if err == nil {
		return
	}
	if errors.Is(err, context.DeadlineExceeded) {
		this.lk.Lock()
		this.resetConnection()
		this.lk.Unlock()
	}
	return
}

func (this *S3ClientWrapper) UploadFile(ctx context.Context, localFile string, bucketName, objectKey string) error {
	file, err := os.Open(localFile)
	if err != nil {
		return fmt.Errorf("open localFile fail %s: %v", localFile, err)
	}
	defer file.Close()

	return this.PutObject(ctx, bucketName, objectKey, file)
}

func (this *S3ClientWrapper) DownFile(ctx context.Context, localFile string, bucketName, objectKey string) error {
	// 确保下载目录存在
	err := gobase.ForceCreateFilePath(localFile)
	if err != nil {
		return fmt.Errorf("create dir of localfile: %v", err)
	}

	outFile, err := os.Create(localFile)
	if err != nil {
		return fmt.Errorf("create localfile %s: %v", localFile, err)
	}
	defer outFile.Close()

	return this.GetObject(ctx, bucketName, objectKey, outFile)
}

//func (this *S3ClientWrapper) MultipartUpload(ctx context.Context, bucketName, objectKey string, input io.Reader) error {
//	clt, err := this.getOrCreateClient()
//	if err != nil {
//		return err
//	}
//
//	//_, err = clt.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
//	//	Bucket: aws.String(bucketName),
//	//	Key:    aws.String(objectKey),
//	//	MultipartUpload: &types.CompletedMultipartUpload{
//	//		Parts: completedParts,
//	//	},
//	//})
//	//this.checkInvalidConnectionError(err)
//
//	if err != nil {
//		return fmt.Errorf("Put Object Fail: %v", err)
//	}
//	return err
//}

func (this *S3ClientWrapper) PutObject(ctx context.Context, bucketName, objectKey string, input io.Reader) error {
	clt, err := this.getOrCreateClient()
	if err != nil {
		return err
	}

	_, err = clt.PutObject(ctx, &s3.PutObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(objectKey),
		Body:   input,
	})
	this.checkInvalidConnectionError(err)

	if err != nil {
		return fmt.Errorf("Put Object Fail: %v", err)
	}
	return err
}

func (this *S3ClientWrapper) GetObject(ctx context.Context, bucketName, objectKey string, output io.Writer) error {
	clt, err := this.getOrCreateClient()
	if err != nil {
		return err
	}

	result, err := clt.GetObject(ctx, &s3.GetObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(objectKey),
	})
	if err != nil {
		return fmt.Errorf("GetObject Fail: %v", err)
	}

	defer result.Body.Close()

	_, err = io.Copy(output, result.Body)
	return err
}

func (this *S3ClientWrapper) DeleteObject(ctx context.Context, bucketName, objectKey string) error {
	clt, err := this.getOrCreateClient()
	if err != nil {
		return err
	}

	_, err = clt.DeleteObject(ctx, &s3.DeleteObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(objectKey),
	})
	if err != nil {
		return fmt.Errorf("delete file fail: %v", err)
	}
	return nil
}

// endpoint=&region=&accessKeyID=
func createOSSOperator(args ...interface{}) (rval interface{}, err error) {
	if len(args) == 0 {
		err = fmt.Errorf("无效的参数")
		return
	}
	if conf, ok := args[0].(gobase.StrMap); ok {
		s3c := NewS3ClientWrapper()
		s3c.UpdateConnString(conf.URLEncode0())
		return s3c, nil
	} else if str, ok1 := args[0].(string); ok1 {
		s3c := NewS3ClientWrapper()
		s3c.UpdateConnString(str)
		return s3c, nil
	} else {
		err = fmt.Errorf("无效的参数")
		return
	}
}

func init() {
	factory.RegisterCreateFunc("oss.s3client", createOSSOperator)
}
